/**
 * Copyright 2009 University of  Washington
 * This code is released under the GPL version 3 - with a copy of the license included
 * @author Maya Hao Li
 *
 */
package seedpod.model;

import java.sql.ResultSet;
import java.sql.SQLException;

import seedpod.exceptions.SeedpodException;
import seedpod.kb2db.ClsMap;
import seedpod.kb2db.SlotMap;
import seedpod.model.rdb.RdbValueType;
import seedpod.model.rdb.Relation;
import seedpod.rdb.PManager;
import seedpod.rdb.PersistenceDO;
import seedpod.rdb.RdbSql;
import seedpod.rdb.SqlUtil;

/**
 * this class can not extend SeedpodDO because it doesn't have its attributes in SlotMaps collection. either fix that as
 * a bug or just implement the interface
 * 
 * @author Hao Li
 */
public class Thing implements PersistenceDO {

	protected String _classType;
	protected Integer _oid = UNKNOWN_OID;
	protected String _status;
	protected PManager _manager;
	protected Integer _invalidOid = UNKNOWN_OID;

	public static String STATUS_DELETED = "deleted";
	public static String STATUS_SAVED = "saved";
	public static String STATUS_INVALID = "invalid";

	protected boolean _isDirty = false;

	/**
	 * classTyep refers to the the object, not "THING"
	 * 
	 * @param classType
	 * @param manager
	 */

	public Thing(String classType, Integer oid, PManager manager) throws NoObjectFoundException {
		_classType = classType;
		_manager = manager;
		_oid = oid;
		if ( _oid != 0 )
			try {
				retrieveByOid();
			}
			catch (SeedpodException e) {
				e.printStackTrace();
				System.err.println("error retrieving Thing instance with oid " + oid );
				markInvalid();
			}
		else {
			markDirty();
			markInvalid();
		}

		if ( !isValidClassType( _classType, manager ) )
			throw new NoObjectFoundException();

	}

	private boolean isValidClassType( String classType, PManager manager ) {
		ClsMap cmap = manager.getModelMap().getClsMap( classType );
		return (cmap != null);
	}

	public Thing(Integer oid, PManager manager) throws NoObjectFoundException {
		_classType = getClassType( manager, oid );
		_oid = oid;
		_manager = manager;
		if ( _oid != 0 )
			try {
				retrieveByOid();
			}
			catch (SeedpodException e) { 
				markInvalid();
				throw new NoObjectFoundException(_oid ) ;
			}
		else
			markInvalid();
	}

	public Thing(String type, Integer oid, String status, PManager manager) {
		_classType = type;
		_oid = oid;
		_manager = manager;
		_status = status;
		_isDirty = false;
	}

	public Integer getNextGlobalOid() throws SeedpodException {
		Integer oid = 0;

		try {
			String seqName = SeedpodModel.RdbCls.THING + "_" + SeedpodModel.RdbSlot.THING_ID + "_seq";
			String seqSql = RdbSql.SELECT + " nextval " + SqlUtil.PARAN( SqlUtil.Q( SqlUtil.QQ( seqName ) ) );
			ResultSet rs = _manager.getRdbConnection().executeQuery( seqSql );

			if ( rs != null & rs.next() )
				oid = rs.getInt( 1 );
		}
		catch (SQLException e) {
			throw new SeedpodException( e, "Problem generating unique ID  for new " + _classType );
		}

		if ( oid.intValue() == 0 )
			throw new SeedpodException( "Problem generating unique ID  for new " + _classType );

		System.err.println( "OID = " + oid );
		return oid;
	}

	public boolean create() throws SeedpodException {
		_oid = getNextGlobalOid();

		String value = _oid + SqlUtil.C;
		value += RdbSql.toSqlValString( _classType, RdbValueType.VARCHAR ) + SqlUtil.C;
		value += RdbSql.toSqlValString( STATUS_SAVED, RdbValueType.VARCHAR );

		String sql = RdbSql.INSERT_INTO + SqlUtil.QQ( SeedpodModel.RdbCls.THING ) + RdbSql.VALUES;
		sql += SqlUtil.PARAN( value );

		System.err.println( "Create ThingInstance sql: " + sql );

		int result = 0;
		try {
			result = _manager.getRdbConnection().executeUpdate( sql );
			_isDirty = false;
		}
		catch (SQLException e) {
			System.err.println( "failed to create thingInstance" );
			_isDirty = true;
			throw new SeedpodException( e, "create thing object failed" );
		}
		return (result > 0);
	}

	public Integer getOid() {
		return _oid;
	}

	public String getStatus() {
		return _status;
	}

	public String getClassType() {
		return _classType;
	}

	public static String getClassType( PManager manager, int oid ) throws NoObjectFoundException {
		ResultSet rs;
		String classType = "";
		StringBuffer sql = new StringBuffer();

		sql.append( RdbSql.SELECT );
		sql.append( SqlUtil.QQ( SeedpodModel.RdbSlot.THING_TYPE ) );
		sql.append( RdbSql.FROM );
		sql.append( SqlUtil.QQ( SeedpodModel.RdbCls.THING ) );
		sql.append( RdbSql.WHERE );
		sql.append( SqlUtil.QQ( Relation.DEFAULT_PRIMARY_KEY ) + RdbSql.EQUALS + oid );

		try {
			System.err.println( sql.toString() );
			rs = manager.getRdbConnection().executeQuery( sql.toString() );
			rs.next();
			classType = rs.getString( SeedpodModel.RdbSlot.THING_TYPE );
		}
		catch (SQLException exc) {
			throw new NoObjectFoundException( oid );
		}
		return classType;
	}

	// delete of an object merely marks deletion. No object is deleted
	public boolean delete() throws SeedpodException {
		_status = STATUS_DELETED;
		return update();
	}

	public AVPair getAVPair( SlotMap attribute ) {
		// do nothing
		return null;
	}

	public String getState() {
		return _status;
	}

	public boolean isDirty() {
		return _isDirty;
	}

	public boolean isDeleted() {
		return _status.equalsIgnoreCase( STATUS_DELETED );
	}

	public boolean isPersistent() {
		return _status.equalsIgnoreCase( STATUS_SAVED );
	}

	public boolean isInvalid() {
		return _status.equalsIgnoreCase( STATUS_INVALID );
	}

	public void load( ResultSet rs ) throws SeedpodException {
			if ( rs == null  ) {
				throw new SeedpodException( "No result set. " );
			}
	
		try {
			_classType = rs.getString( SeedpodModel.RdbSlot.THING_TYPE );
			_oid = new Integer( rs.getInt( SeedpodModel.RdbSlot.THING_ID ) );
			_status = rs.getString( SeedpodModel.RdbSlot.THING_STATUS );
		}
		catch (SQLException sqle) {
			throw new SeedpodException( sqle, "Error loading Thing Object" );
		}

		try {
			rs.close();
		}
		catch (SQLException e) {
			throw new SeedpodException(e, "Error closing SQL statement while retrieving Thing Instance with oid " + _oid ); 
		}

	}

	public void markInvalid() {
		_status = STATUS_INVALID;
	}

	// same as makeTransient
	public void markDirty() {
		_isDirty = true;
	}

	public boolean retrieveByOid() throws SeedpodException {
		String filter = SqlUtil.QQ(  SeedpodModel.RdbSlot.THING_ID ) + RdbSql.EQUALS + _oid;
		return retrieve( filter );
	}

	public boolean retrieve( String queryFilter ) throws SeedpodException {
		boolean success = false;
		StringBuffer query = RdbSql.selectSql( SeedpodModel.RdbCls.THING, null, queryFilter );
		ResultSet rs;
		try {
			rs = _manager.getRdbConnection().executeQuery( query.toString() );
			rs.next();
			load( rs );
			success = true;
		}
		catch (SQLException e) {
			success = false;
			e.printStackTrace();
			throw new SeedpodException( e, "Error retrieving thing object. query=" + query );
		}
		return success;
	}

	public void setAttributeValue( SlotMap attribute, Object value ) throws NullSlotMapException {

	}

	public boolean update() throws SeedpodException {

		String sql = RdbSql.UPDATE + SqlUtil.QQ( SeedpodModel.RdbCls.THING ) + RdbSql.SET;
		sql += SqlUtil.QQ( SeedpodModel.RdbSlot.THING_TYPE ) + RdbSql.EQUALS + SqlUtil.DQ( _classType ) + SqlUtil.C;
		sql += SqlUtil.QQ( SeedpodModel.RdbSlot.THING_STATUS ) + RdbSql.EQUALS + SqlUtil.DQ( _status ) + SqlUtil.SP;
		sql += RdbSql.WHERE + SqlUtil.QQ( SeedpodModel.RdbSlot.THING_ID ) + RdbSql.EQUALS + _oid;

		System.err.println( "ThingInstance.update() " + sql );
		int queryResult = 0;
		try {
			queryResult = _manager.getRdbConnection().executeUpdate( sql );
			_isDirty = false;
		}
		catch (SQLException e) {
			throw new SeedpodException( e, "SQLException in ThingInstance() update: " + e.getMessage() );
		}

		return (queryResult > 0);
	}

	public void makePersist() throws SeedpodException {
		_status = STATUS_SAVED;
		_isDirty = false;
	}

}
